package com.rxt.thermopro.viewmodel

import android.app.Dialog
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.view.Window
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.app.common.ex.toast
import com.app.common.viewmodel.BaseViewModel
import com.app.common.viewmodel.EventLiveData
import com.rxt.ble.BleManager
import com.rxt.thermopro.R
import com.rxt.thermopro.app.LocalDataDao
import com.rxt.thermopro.bean.RingBean
import com.rxt.thermopro.ex.Converters
import com.rxt.thermopro.ex.OtaFileType
import com.rxt.thermopro.ex.UuidConsts
import com.rxt.thermopro.ui.fragment.TP960SettingFragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.util.*

class TP960SettingViewModel : BaseViewModel() {

    val probeAlarmList: MutableLiveData<List<RingBean>> = MutableLiveData()

    fun getProbeAlarmList() {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                val list = LocalDataDao.INSTANCE.getProbeAlarmList()
                withContext(Dispatchers.Main) {
                    probeAlarmList.value = list
                }
            }
        }
    }

    var ota_mode = false
    private var homekit = false
    private var mAddresses = ""
    private var doubleStepUpload = false
    private var boolFullOTA = false
    private var currentOtaFileType: OtaFileType? = null

    // OTA file paths
    var appPath = ""
    private var stackPath = ""
    private val handler = Handler(Looper.getMainLooper())
    private val WRITE_OTA_CONTROL_ZERO = Runnable { writeOtaControl(0x00.toByte()) }
    var bluetoothGatt: BluetoothGatt? = null
    private var kit_descriptor: BluetoothGattDescriptor? = null
    private var otafile: ByteArray? = null
    private var pack = 0
    private var reliable = true
    private var MTU = 247
    private var mtuDivisible = 0
    private var otatime: Long = 0
    private var boolOTAdata = false
    private var delayNoResponse = 1
    private var ota_process=false
    private var boolOTAbegin=false
    private var disconnect_gatt=false
    var UICreated = false
    private var loadingdialog: Dialog? = null
    private var bluetoothDevice: BluetoothDevice? = null
    private var disconnectionTimeout=false
    private var retryAttempts = 0

    private val DFU_OTA_UPLOAD = Runnable { dfuMode("OTAUPLOAD") }


    val dataRateValue:EventLiveData<String> = EventLiveData()


    fun startOTAUpload(){
        viewModelScope.launch {
            if (ota_mode) {
                bluetoothGatt?.requestMtu(250)
            } else dfuMode("OTABEGIN")
        }
    }

    /**
     * OTA STATE MACHINE
     */
    @Synchronized
    fun dfuMode(step: String?) {
        when (step) {
            "INIT" -> dfuMode("OTABEGIN")
            "OTABEGIN" -> if (ota_mode) {
                //START OTA PROCESS -> gattCallback -> OnCharacteristicWrite
                Log.d("OTA_BEGIN", "true")
                handler.postDelayed(WRITE_OTA_CONTROL_ZERO, 200)
            } else {
                if (homekit) {
                    bluetoothGatt?.readDescriptor(kit_descriptor)
                } else {
                    Log.d("DFU_MODE", "true")
                    handler.postDelayed(WRITE_OTA_CONTROL_ZERO, 200)
                }
            }
            "OTAUPLOAD" -> {
                Log.d("OTAUPLOAD", "Called")
                /**Check Services */
                val mBluetoothGattService = bluetoothGatt?.getService(UuidConsts.OTA_SERVICE)
                if (mBluetoothGattService != null) {
                    val charac =
                        bluetoothGatt?.getService(UuidConsts.OTA_SERVICE)!!.getCharacteristic(
                            UuidConsts.OTA_DATA
                        )
                    if (charac != null) {
                        charac.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
                        Log.d("Instance ID", "" + charac.instanceId)
                        /**Check Files */
                        var ebl: ByteArray? = null
                        try {
                            Log.d("appPath", "" + appPath)
                            val file: File
                            if (stackPath != "" && doubleStepUpload) {
                                file = File(stackPath)
                                boolFullOTA = true
                            } else {
                                file = File(appPath)
                                boolFullOTA = false
                            }
                            val fileInputStream = FileInputStream(file)
                            val size = fileInputStream.available()
                            Log.d("size", "" + size)
                            val temp = ByteArray(size)
                            fileInputStream.read(temp)
                            fileInputStream.close()
                            ebl = temp
                        } catch (e: Exception) {
                            Log.e("InputStream", "Couldn't open file$e")
                        }
                        val datathread = ebl
                        otafile = ebl
                        /**Check if it is partial of full OTA */
                        val fn: String
                        if (stackPath != "" && doubleStepUpload) {
                            val last = stackPath.lastIndexOf(File.separator)
//                            fn = getString(R.string.ota_filename_s, stackPath.substring(last).removePrefix("/"))
                            fn = "fileName"
                            Log.d("CurrentlyUpdating", "apploader")
                        } else {
                            val last = appPath.lastIndexOf(File.separator)
//                            fn = getString(R.string.ota_filename_s, appPath.substring(last).removePrefix("/"))
                            fn = "fileName"
                            Log.d("CurrentlyUpdating", "appliaction")
                        }
                        pack = 0
                        /**Prepare information about current upload step */
                        val stepInfo: String
                        if (doubleStepUpload) {
                            if (stackPath != "") {
                                stepInfo = "1 OF 2"
                            } else {
                                stepInfo = "2 OF 2"
                            }
                        } else {
                            stepInfo = "1 OF 1"
                        }
                        /**Set info into UI OTA Progress */
//                        runOnUiThread {
//                            filename?.text = fn
//                            steps?.text = stepInfo
//                            sizename?.text = datathread?.size.toString() + " bytes"
//                            mtuname?.text = MTU.toString()
//                            uploadimage?.visibility = View.VISIBLE
//                            animaloading()
//                        }
                        /**Start OTA_data Upload in another thread */
                        val otaUpload = Thread(Runnable {
                            otaWriteDataReliable()
                        })
                        otaUpload.start()
                    }
                }
            }
            "OTAEND" -> {
                Log.d("OTAEND", "Called")
                handler.postDelayed({ writeOtaControl(0x03.toByte()) }, 500)
            }
            "DISCONNECTION" -> {
                ota_process = false
                boolFullOTA = false
                boolOTAbegin = false
                disconnectGatt(bluetoothGatt)
            }
            else -> {
            }
        }
    }

    /**
     * WRITES EBL/GBL FILES TO OTA_DATA CHARACTERISTIC
     */
    @Synchronized
    fun otaWriteDataReliable() {
        boolOTAdata = true
        if (pack == 0) {
            /**SET MTU_divisible by 4 */
            var minus = 0
            do {
                mtuDivisible = MTU - 3 - minus
                minus++
            } while (mtuDivisible % 4 != 0)
            viewModelScope.launch {
                withContext(Dispatchers.Main) {
//                    mtuname?.text = "$mtuDivisible bytes"
                    println("123-> ota  mtuname=$mtuDivisible bytes")
                }
            }
        }
        val writearray: ByteArray
        val pgss: Float
        if (pack + mtuDivisible > otafile?.size!! - 1) {
            /**SET last by 4 */
            var plus = 0
            var last = otafile?.size!! - pack
            do {
                last += plus
                plus++
            } while (last % 4 != 0)
            writearray = ByteArray(last)
            for ((j, i) in (pack until pack + last).withIndex()) {
                if (otafile?.size!! - 1 < i) {
                    writearray[j] = 0xFF.toByte()
                } else writearray[j] = otafile!![i]
            }
            pgss = ((pack + last).toFloat() / (otafile?.size!! - 1)) * 100
            Log.d(
                "characte", "last: " + pack + " / " + (pack + last) + " : " +
                        Converters.bytesToHexWhitespaceDelimited(writearray)
            )
        } else {
            var j = 0
            writearray = ByteArray(mtuDivisible)
            for (i in pack until pack + mtuDivisible) {
                writearray[j] = otafile!![i]
                j++
            }
            pgss = ((pack + mtuDivisible).toFloat() / (otafile?.size!! - 1)) * 100
            Log.d(
                "characte", "pack: " + pack + " / " + (pack + mtuDivisible) + " : " +
                        Converters.bytesToHexWhitespaceDelimited(writearray)
            )
        }
        val charac = bluetoothGatt?.getService(UuidConsts.OTA_SERVICE)
            ?.getCharacteristic(UuidConsts.OTA_DATA)
        charac?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
        charac?.value = writearray
        bluetoothGatt?.writeCharacteristic(charac)
        val waiting_time = (System.currentTimeMillis() - otatime)
        val bitrate = 8 * pack.toFloat() / waiting_time
        if (pack > 0) {
            handler.post {
//                runOnUiThread {
//                    progressBar?.progress = pgss.toInt()
                    val datarate = String.format(Locale.US, "%.2fkbit/s", bitrate)
                println("123->  ota  datarate=$datarate")
//                    dataRate?.text = datarate
//                    datasize?.text = pgss.toInt().toString() + " %"
//                }
            }
        } else {
            otatime = System.currentTimeMillis()
        }
    }

    @Synchronized
    fun whiteOtaData(datathread: ByteArray?) {
        try {
            boolOTAdata = true
            val value = ByteArray(MTU - 3)
            val start = System.nanoTime()
            var j = 0
            for (i in datathread?.indices!!) {
                value[j] = datathread[i]
                j++
                if (j >= MTU - 3 || i >= (datathread.size - 1)) {
                    var wait = System.nanoTime()
                    val charac = bluetoothGatt?.getService(UuidConsts.OTA_SERVICE)?.getCharacteristic(
                        UuidConsts.OTA_DATA
                    )
                    charac?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
                    val progress = ((i + 1).toFloat() / datathread.size) * 100
                    val bitrate = (((i + 1) * (8.0)).toFloat() / (((wait - start) / 1000000.0).toFloat()))
                    if (j < MTU - 3) {
                        val end = ByteArray(j)
                        System.arraycopy(value, 0, end, 0, j)
                        Log.d("Progress", "sent " + (i + 1) + " / " + datathread.size + " - " + String.format("%.1f", progress) + " % - " + String.format("%.2fkbit/s", bitrate) + " - " + Converters.bytesToHexWhitespaceDelimited(end))
                        viewModelScope.launch {
                            withContext(Dispatchers.Main){
                                //todo
//                                datasize?.text = progress.toInt().toString() + " %"
//                                progressBar?.progress = progress.toInt()
                            }
                        }
                        charac?.value = end
                    } else {
                        j = 0
                        Log.d("Progress", "sent " + (i + 1) + " / " + datathread.size + " - " + String.format("%.1f", progress) + " % - " + String.format("%.2fkbit/s", bitrate) + " - " + Converters.bytesToHexWhitespaceDelimited(value))
                        viewModelScope.launch {
                            withContext(Dispatchers.Main){
                                //todo
//                                datasize?.text = progress.toInt().toString() + " %"
//                                progressBar?.progress = progress.toInt()
                            }
                        }
                        charac?.value = value
                    }
                    if (bluetoothGatt?.writeCharacteristic(charac)!!) {
                        viewModelScope.launch {
                            withContext(Dispatchers.Main){
                                //todo
                                val datarate = String.format(Locale.US, "%.2fkbit/s", bitrate)
//                                dataRate?.text = datarate
                                dataRateValue.postValue(datarate)
                            }
                        }
                        while ((System.nanoTime() - wait) / 1000000.0 < delayNoResponse);
                    } else {
                        do {
                            while ((System.nanoTime() - wait) / 1000000.0 < delayNoResponse);
                            wait = System.nanoTime()
                            viewModelScope.launch {
                                withContext(Dispatchers.Main){
                                    //todo
                                    val datarate = String.format(Locale.US, "%.2fkbit/s", bitrate)
//                                dataRate?.text = datarate
                                    dataRateValue.postValue(datarate)
                                }
                            }
                        } while (!bluetoothGatt?.writeCharacteristic(charac)!!)
                    }
                }
            }
            val end = System.currentTimeMillis()
            val time = (end - start) / 1000L.toFloat()
            Log.d("OTA Time - ", "" + time + "s")
            boolOTAdata = false
            viewModelScope.launch {
                withContext(Dispatchers.Main){
//                    chrono?.stop()
//                    uploadimage?.clearAnimation()
//                    uploadimage?.visibility = View.INVISIBLE
                }
            }
            dfuMode("OTAEND")
        } catch (e: NullPointerException) {
            e.printStackTrace()
        }
    }

    /**
     * CREATES BAR PROGRESS ANIMATION IN LOADING AND OTA PROGRESS DIALOG
     */
    private fun animaloading() {
//        if ((uploadimage != null) && (loadingimage != null) && (otaProgress != null)) {
//            uploadimage?.visibility = View.GONE
//            loadingimage?.visibility = View.GONE
//            if (loadingdialog?.isShowing!!) {
//                loadingimage?.visibility = View.VISIBLE
//            }
//            if (otaProgress?.isShowing!!) {
//                uploadimage?.visibility = View.VISIBLE
//            }
//        }
    }


    /**
     * WRITES BYTE TO OTA CONTROL CHARACTERISTIC
     */
    private fun writeOtaControl(ctrl: Byte): Boolean {
        Log.d("writeOtaControl", "Called")
        if (bluetoothGatt?.getService(UuidConsts.OTA_SERVICE) != null) {
            val charac =
                bluetoothGatt?.getService(UuidConsts.OTA_SERVICE)?.getCharacteristic(
                    UuidConsts.OTA_CONTROL
                )
            if (charac != null) {
                Log.d("Instance ID", "" + charac.instanceId)
                charac.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
                Log.d("charac_properties", "" + charac.properties)
                val control = ByteArray(1)
                control[0] = ctrl
                charac.value = control
                bluetoothGatt?.writeCharacteristic(charac)
                return true
            } else {
                Log.d("characteristic", "null")
            }
        } else {
            Log.d("service", "null")
        }
        return false
    }


    /**
     * DISCONNECT GATT GENTLY AND CLEAN GLOBAL VARIABLES
     */
    fun disconnectGatt(gatt: BluetoothGatt?) {
        val disconnectTimer = Timer()
        boolFullOTA = false
        boolOTAbegin = false
        ota_process = false
        disconnect_gatt = true
        UICreated = false

        if (gatt != null && gatt.device != null) {
            if (loadingdialog == null) {
                initLoading()
            }

            val btGatt: BluetoothGatt = gatt
            disconnectTimer.schedule(object : TimerTask() {
                override fun run() {
                    /**Getting bluetoothDevice to FetchUUID */
                    if (btGatt.device != null) bluetoothDevice = btGatt.device
                    /**Disconnect gatt */
//                    btGatt.disconnect()
                    val connected = BleManager.getInstance().allConnectedDevice
                    if (connected.isNotEmpty()) {
                        val list = connected.filter { it.mac == mAddresses }
                        if (list.isNotEmpty()){
                            BleManager.getInstance().disconnect(list.first())
                        }
                    }
                    Log.d("disconnectGatt", "gatt disconnect")
                    viewModelScope.launch {
                        withContext(Dispatchers.Main){
                            //todo
//                            showLoading()
//                            loadingLog?.text = "Disconnecting..."
//                            loadingHeader?.text = "GATT Connection"
                        }
                    }
                }
            }, 200)
            disconnectTimer.schedule(object : TimerTask() {
                override fun run() {
                    bluetoothDevice?.fetchUuidsWithSdp()
                }
            }, 300)
            disconnectionTimeout = true
            val timeout = Runnable {
                handler.postDelayed({
                    if (disconnectionTimeout) {
//                        finish()
//                        runOnUiThread { showMessage("DISCONNECTION PROBLEM") }
                    }
                }, 5000)
            }
            Thread(timeout).start()
        } else {
//            finish()
        }
    }

    fun onceAgain() {
        writeOtaControl(0x00.toByte())
    }

    /**
     * INITIALIZES LOADING DIALOG
     */
    private fun initLoading() {
//        loadingdialog = Dialog(this)
//        loadingdialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
//        loadingdialog?.setContentView(R.layout.dialog_loading)
//
//        loadingimage = loadingdialog?.findViewById(R.id.connecting_spinner)
//        loadingLog = loadingdialog?.findViewById(R.id.loadingLog)
//        loadingHeader = loadingdialog?.findViewById(R.id.loading_header)
    }

    fun uploadFile() {
        handler.removeCallbacks(DFU_OTA_UPLOAD)
        handler.postDelayed(DFU_OTA_UPLOAD, 500)
    }

    fun uploadComplete() {
        pack += mtuDivisible
        if (pack <= otafile?.size!! - 1) {
            otaWriteDataReliable()
        } else if (pack > otafile?.size!! - 1) {
//            handler.post {
//                runOnUiThread {
//                    chrono?.stop()
//                    uploadimage?.clearAnimation()
//                    uploadimage?.visibility = View.INVISIBLE
//                }
//            }
            boolOTAdata = false
            retryAttempts = 0
            dfuMode("OTAEND")
        }
    }

}